#include <string>
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#ifndef CXX_LESS_17
#include <filesystem>
#else
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#endif
#include <unordered_map>
#include <vector>
#include "ExchangeIO.h"
#include "ExchangeParameter.h"

#include "include/cxxopts.hpp"


std::string  GetSystemEnv(const std::string &envName){
    const char *envValue = std::getenv(envName.c_str());
    if (envValue != nullptr)
    {
        std::cout << envName << ": " << envValue << std::endl;
        return envValue ;
    }
    else
    {
        std::cout << envName << " is not set." << std::endl;
    }
    return "" ;
}

bool IsDirExist(const std::string &path)
{
#ifndef CXX_LESS_17
        return std::filesystem::exists(path) && std::filesystem::is_directory(path);
#else
        struct stat buffer;
        // 检查路径是否存在且是一个目录
        if (stat(path.c_str(), &buffer) == 0) {
            return S_ISDIR(buffer.st_mode);  // 判断是否是目录
        }
        return false;  // 如果路径不存在或不是目录
#endif
}

#ifdef CXX_LESS_17
bool RemoveDir(const std::string& path) {
    struct stat statbuf;
    if (stat(path.c_str(), &statbuf) != 0) {
        std::cerr << "Error getting status of file or directory: " << path << std::endl;
        return false;
    }

    if (S_ISDIR(statbuf.st_mode)) {
        // 如果是目录，递归删除目录内容
        DIR* dir = opendir(path.c_str());
        if (dir == nullptr) {
            std::cerr << "Unable to open directory: " << path << std::endl;
            return false;
        }

        struct dirent* entry;
        while ((entry = readdir(dir)) != nullptr) {
            // Skip hidden files (dotfiles)
            if (entry->d_name[0] != '.') {
                // 拼接完整路径
                std::string fullPath = path + "/" + entry->d_name;
                
                // 递归删除文件/子目录
                RemoveDir(fullPath);
            }
        }
        closedir(dir);

        // 删除目录
        if (rmdir(path.c_str()) != 0) {
            std::cerr << "Error deleting directory: " << path << std::endl;
            return false;
        }
    } else if (S_ISREG(statbuf.st_mode)) {
        // 如果是文件，直接删除
        if (remove(path.c_str()) != 0) {
            std::cerr << "Error deleting file: " << path << std::endl;
            return false;
        }
    } else {
        std::cerr << "Unknown file type: " << path << std::endl;
        return false;
    }

    return true;
}

void MakeDir(const std::string &path)
{
    if (mkdir(path.c_str(), S_IRWXU) == 0) {
        std::cout << "Directory created successfully!" << std::endl;
    } else {
        std::cerr << "Error creating directory: " << std::endl;
    }
}

#endif

#ifndef CXX_LESS_17
void TraverseDirectory(const std::filesystem::path &path, std::vector<std::string> *containers)
{
    for (const auto &entry : std::filesystem::directory_iterator(path))
    {
        if (entry.is_directory())
        {
            TraverseDirectory(entry.path(), containers); // 递归遍历子目录
        }
        else
        {
            containers->emplace_back(entry.path().string());
        }
    }
}
#else
void TraverseDirectory(const std::string& path, std::vector<std::string> *containers)
{
   DIR* dir = opendir(path.c_str());
    if (dir == nullptr) {
        std::cerr << "Unable to open directory: " << path << std::endl;
        
        return;
    }

    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        // Skip hidden files (dotfiles)
        if (entry->d_name[0] != '.') {
            // Combine the directory path and file name
            std::string filePath = path + "/" + entry->d_name;

            // 获取绝对路径
            char absPath[PATH_MAX];
            if (realpath(filePath.c_str(), absPath)) {
                // std::cout << absPath << std::endl;

                // 如果是目录，递归遍历
                struct stat statbuf;
                if (stat(absPath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
                    // 如果是目录，递归进入子目录
                    TraverseDirectory(absPath,containers);
                }else{
                    containers->emplace_back(absPath);
                }
            } else {
                std::cerr << "Error resolving absolute path for " << filePath << std::endl;
            }
        }
    }

    closedir(dir);
}
#endif

std::string GetFilenameWithoutExtension(const std::string &path)
{
#ifndef CXX_LESS_17
        if (path.empty()) {
            return "" ;
        }
        return std::filesystem::path(path).stem().string();
#else
        // 获取文件名起始位置（最后一个 / 或 \ 之后）
        size_t slashPos = path.find_last_of("/\\");
        size_t start = (slashPos == std::string::npos) ? 0 : slashPos + 1;

        // 获取扩展名前的位置（最后一个点）
        size_t dotPos = path.find_last_of('.');

        // 点如果在文件名部分之外（例如在目录名中），忽略
        if (dotPos == std::string::npos || dotPos < start) {
            dotPos = path.length();
        }

        return path.substr(start, dotPos - start);
#endif
}

int Parse(int argc, const char *argv[], std::unordered_map<std::string, std::string> &parameters)
{
    try
    {
        std::shared_ptr<cxxopts::Options> allocated(new cxxopts::Options(argv[0], " - This is a client program used for testing file exchange."));
        auto &options = *allocated;
        options.positional_help("[optional args]")
            .show_positional_help();

        bool apple = false;

        options.set_width(70)
            .set_tab_expansion()
            .allow_unrecognised_options()
            .add_options()("i,input", "Input file", cxxopts::value<std::string>())("o,output", "Output file", cxxopts::value<std::string>())("h,help", "Print help");

        auto result = options.parse(argc, argv);

        if (result.count("help"))
        {
            std::cout << options.help({"", "Group"}) << std::endl;
            return true;
        }

        if (!result.count("input"))
        {
            throw cxxopts::exceptions::exception("Error: -i,--input is required");
        }
        parameters["input"] = result["input"].as<std::string>();

        if (!result.count("output"))
        {
            throw cxxopts::exceptions::exception("Error: -o,--output is required");
        }
        parameters["output"] = result["output"].as<std::string>();
    }
    catch (const cxxopts::exceptions::exception &e)
    {
        std::cout << "error parsing options: " << e.what() << std::endl;
        return false;
    }

    return true;
}

std::string ToLowerCase(const std::string s)
{
    std::string lower_str = s;
    std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(), ::tolower);
    return lower_str;
}

bool IsFileExist(const std::string &path)
    {
#ifndef CXX_LESS_17
        return std::filesystem::exists(path) && std::filesystem::is_regular_file(path);
#else 
        struct stat buffer;
        return (stat(path.c_str(), &buffer) == 0); // 如果返回0，路径存在
#endif
    }

std::string GetExtension(const std::string &path)
{
#ifndef CXX_LESS_17
    return ToLowerCase(std::filesystem::path(path).extension().string());
#else
    // 找最后一个斜杠（表示路径分隔）
    size_t slashPos = path.find_last_of("/\\");
    size_t dotPos = path.find_last_of('.');

    // 点不存在或在路径前面，说明没有扩展名
    if (dotPos == std::string::npos || (slashPos != std::string::npos && dotPos < slashPos)) {
        return "";
    }

    return path.substr(dotPos);
#endif
    
}

std::string TrimStr(std::string s) {
    std::string current{};
    for (char c : s)
    {
        if (c == ' ')
        {
            continue;
        }
        else
        {
            current += c;
        }
    }
    return current;
}

bool IsEqualIgnoreCaseAfterTrimStr(const std::string &s1, const std::string &s2) {
    return TrimStr(ToLowerCase(s1)) == TrimStr(ToLowerCase(s2));
}

void Convert(const std::string &infilePath ,const std::string &outFilePath){
    std::cout << "input:" << infilePath << "   output: " << outFilePath << std::endl ;
    AMCAX::Exchange::ExchangeIO::Convert(infilePath, outFilePath);

    AMCAX::Exchange::ExchangeIO::SetReporter([=](const AMCAX::Exchange::OperationType &type, const int &v){
        std::cout << "process" << v<< std::endl ;
    }) ;
    std::string meteDataOn = GetSystemEnv("AMCAX_METADATA_ON") ;
    if(IsEqualIgnoreCaseAfterTrimStr(meteDataOn,"on"))
    {
        auto intputParams = std::make_shared<AMCAX::Exchange::InterOpInputParameter>(AMCAX::Exchange::INTEROP_META_FACET_TYPE::FACET_TYPE_TRIANGLE, 0);
        auto params = AMCAX::Exchange::ExchangeIO::GetMetaData(infilePath, outFilePath, intputParams);       
        std::shared_ptr<AMCAX::Exchange::InterOpResultParameter> resultParams = std::dynamic_pointer_cast<AMCAX::Exchange::InterOpResultParameter>(params);
        if(resultParams)
        {
            auto volume = resultParams->GetVolume();
            auto area = resultParams->GetArea();
            auto vertexes = resultParams->GetVertexes();
            auto triangles = resultParams->GetTriangles();
            auto topologyJsonFilePath = resultParams->GetTopologyJsonFilePath();
            std::stringstream ss ;
            ss  << " volume:" << volume ;
            ss  << " area:" << area ;
            ss << " vertexe: \r\n" ;
            for(auto v : vertexes){
                ss << " x: " << v.x ;
                ss << " y: " << v.y ;
                ss << " z: " << v.z ;
                ss << "\r\n" ;
            }
            ss << "triangles\r\n"  ;
            for(auto t : triangles){

                for(auto p: t){
                    ss << " x: " << p.x ;
                    ss << " y: " << p.y ;
                    ss << " z: " << p.z ;
                    ss << "  " ;
                }

                ss << "\r\n" ;
            }

            ss << " topologyJsonFilePath:" << topologyJsonFilePath ; 
            std::cout << ss.str() << std::endl ;
        }    
    }

    std::string documentOn = GetSystemEnv("AMCAX_DOCUMENT_ON") ;

    if(IsEqualIgnoreCaseAfterTrimStr(documentOn,"on"))
    {
        std::string documentInfo  ;
        AMCAX::Exchange::ExchangeIO::GetDocumentInfo(infilePath,documentInfo) ;
        std::cout << "documentInfo:" << documentInfo << std::endl ;
    }

    std::string propertiesOn = GetSystemEnv("AMCAX_PROPERTIES_ON") ;

    if(IsEqualIgnoreCaseAfterTrimStr(propertiesOn,"on"))
    {
        std::string properties ;
        AMCAX::Exchange::ExchangeIO::GetProperties(infilePath,properties) ;
        std::cout << "properties:" << properties << std::endl ;
    }

    std::string pmiReadOn = GetSystemEnv("AMCAX_PMI_READER_ON") ;
    if(IsEqualIgnoreCaseAfterTrimStr(pmiReadOn,"on"))
    {
        std::unordered_map<std::string,AMCAX::Exchange::Core::PMI::ExchangeShapePMIInfos> pmi =  AMCAX::Exchange::ExchangeIO::GetPMI(infilePath) ;
        for (auto p : pmi)
        {
            std::cout << "pmi:" << p.second.ToString() << std::endl ;
        }
    }
    
}

int  UsingCommandLine(int argc, const char *argv[])
{
    std::unordered_map<std::string, std::string> parameters;
    if (!Parse(argc, argv, parameters))
    {
        return 1;
    }

    std::string inputFilePath = parameters["input"];
    std::string outputFilePath = parameters["output"];
    if (inputFilePath.empty() || outputFilePath.empty())
    {
        std::cout << "Input and output files are required." << std::endl;
        return -1;
    }

    #ifndef CXX_LESS_17
    if (!std::filesystem::exists(inputFilePath))
    {
        std::cout << "The input file does not exist ,Please check if the path is correct."  << inputFilePath << std::endl;
        return -1;
    }

    std::filesystem::path outputFile = outputFilePath;
    if (!outputFile.is_relative() && !outputFile.has_parent_path())
    {
        std::cout << "The paraent directory of the output file must exist. Please check the output file path" << std::endl;
        return -1;
    }

    if (!outputFile.has_extension())
    {
        std::cout << "Please check if the output file extension exists." << std::endl;
        return -1;
    }

    std::string extension = outputFile.extension().string();
    #else
    if(!IsFileExist(inputFilePath))
    {
        std::cout << "The input file does not exist ,Please check if the path is correct."  << inputFilePath << std::endl;
        return -1;
    }

    std::string extension = GetExtension(outputFilePath) ;
    #endif
    extension.erase(0, 1);

    Convert(inputFilePath,outputFilePath) ;
    return 0;
}



void UsingPredefinedFolder(){
    #ifndef CXX_LESS_17
    std::filesystem::path dataFolder = GetSystemEnv("DATA_DIR") ;
    if (dataFolder.string().empty())
    #else
    std::string dataFolder = GetSystemEnv("DATA_DIR") ;
    if (dataFolder.empty())
    #endif
    {
        std::cout << "DATA_DIR is not set." << std::endl;
        return;
    }

    #ifndef CXX_LESS_17
    std::filesystem::path outFoler = dataFolder / ("debug");
    
    if (std::filesystem::exists(outFoler))
    {
        std::filesystem::remove_all(outFoler);
    }
    std::filesystem::create_directories(outFoler);
    #else
    std::string outFoler = dataFolder +"/" + ("debug");
    
    if (IsDirExist(outFoler)) {
        RemoveDir(outFoler) ;
    }
    MakeDir(outFoler) ;
    #endif
    std::vector<std::string> fileList;
    TraverseDirectory(dataFolder, &fileList);
    if (fileList.size() <= 0 ) {
        std::cout << "No files found in the directory." << std::endl;
        return;
    }
#if 1
    std::vector<std::string> iopTargetFileTypes = {"step", "iges", "stl", "catpart", "catproduct"} ;

    std::vector<std::string> dataKitFileTypes = {"sldprt", "sldasm","x_t","x_b"};

    std::vector<std::string> amxFileTypes = {"amx","obj"};

    std::vector<std::string> allFileTypes ;
    allFileTypes.insert(allFileTypes.end(), iopTargetFileTypes.begin(), iopTargetFileTypes.end());
    allFileTypes.insert(allFileTypes.end(), dataKitFileTypes.begin(), dataKitFileTypes.end());
    allFileTypes.insert(allFileTypes.end(), amxFileTypes.begin(), amxFileTypes.end());
    // set convert parameters
#else 
    std::vector<std::string> allFileTypes = {"step"};
#endif 
    
    for ( auto &filePath : fileList)
    {
        std::string realFileName = GetFilenameWithoutExtension(filePath);
        // if(realFileName!="Part4") continue;
        for (auto target : allFileTypes)
        {
            #ifndef CXX_LESS_17
            std::filesystem::path currentOutputFolder = outFoler / target;
            if (!std::filesystem::exists(currentOutputFolder))
            {
                std::filesystem::create_directories(currentOutputFolder);
            }

            std::filesystem::path outFilePath = currentOutputFolder / (realFileName + "." + target);
            std::string outFilePathStr = outFilePath.string() ;

            #else
            std::string currentOutputFolder = outFoler + "/" + target ;
            if(!IsDirExist(currentOutputFolder))
            {
                MakeDir(currentOutputFolder) ;
            }
            std::string outFilePathStr = currentOutputFolder + "/" + realFileName + "." + target ;
            #endif
            Convert(filePath,outFilePathStr) ;
            
        }
    }
}



int main(int argc, const char *argv[])
{
    if (argc > 1) {
        UsingCommandLine(argc, argv) ;
    }else{
        UsingPredefinedFolder() ;
    }
    return 0;
}